﻿
// Prism.Modularity.DirectoryModuleCatalog
using Prism.Modularity;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Windows;
/// <summary>
/// Represents a catalog created from a directory on disk.
/// </summary>
/// <remarks>
/// The directory catalog will scan the contents of a directory, locating classes that implement
/// <see cref="T:Prism.Modularity.IModule" /> and add them to the catalog based on contents in their associated <see cref="T:Prism.Modularity.ModuleAttribute" />.
/// Assemblies are loaded into a new application domain with ReflectionOnlyLoad.  The application domain is destroyed
/// once the assemblies have been discovered.
///
/// The directory catalog does not continue to monitor the directory after it has created the initialize catalog.
/// </remarks>
public class AppDirectoryModuleCatalog : ModuleCatalog
{
    private class InnerModuleInfoLoader : MarshalByRefObject
    {
        internal ModuleInfo[] GetModuleInfos(string path)
        {
            DirectoryInfo directory = new DirectoryInfo(path);
            ResolveEventHandler resolveEventHandler = (object sender, ResolveEventArgs args) => OnReflectionOnlyResolve(args, directory);
            AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += resolveEventHandler;
            ModuleInfo[] result = GetNotAlreadyLoadedModuleInfos(IModuleType: AppDomain.CurrentDomain.GetAssemblies().First((Assembly asm) => asm.FullName == typeof(IModule).Assembly.FullName).GetType(typeof(IModule).FullName), directory: directory).ToArray();
           
            AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve -= resolveEventHandler;
            return result;
        }
        private static IEnumerable<ModuleInfo> GetNotAlreadyLoadedModuleInfos(DirectoryInfo directory, Type IModuleType)
        {
            List<Assembly> validAssemblies = new List<Assembly>();
            Assembly[] alreadyLoadedAssemblies = (from p in AppDomain.CurrentDomain.GetAssemblies()
                                                  where !p.IsDynamic
                                                  select p).ToArray();
            foreach (FileInfo fileInfo in (from file in directory.GetFiles("*.dll")
                                           where alreadyLoadedAssemblies.FirstOrDefault((Assembly assembly) => string.Compare(Path.GetFileName(assembly.Location), file.Name, StringComparison.OrdinalIgnoreCase) == 0) == null
                                           select file).ToList())
            {
                try
                {
                    validAssemblies.Add(Assembly.LoadFrom(fileInfo.FullName));
                }
                catch (BadImageFormatException  )
                {
               
                }
            }
            return validAssemblies.SelectMany((Assembly assembly) => from t in assembly.GetExportedTypes().Where(IModuleType.IsAssignableFrom)
                                                                     where t != IModuleType
                                                                     where !t.IsAbstract
                                                                     select t into type
                                                                     select CreateModuleInfo(type));
        }
        private static Assembly OnReflectionOnlyResolve(ResolveEventArgs args, DirectoryInfo directory)
        {
            Assembly loadedAssembly = AppDomain.CurrentDomain.ReflectionOnlyGetAssemblies().FirstOrDefault((Assembly asm) => string.Equals(asm.FullName, args.Name, StringComparison.OrdinalIgnoreCase));
            if (loadedAssembly != null)
            {
                return loadedAssembly;
            }
            AssemblyName assemblyName = new AssemblyName(args.Name);
            string dependentAssemblyFilename = Path.Combine(directory.FullName, assemblyName.Name + ".dll");
            if (File.Exists(dependentAssemblyFilename))
            {
                return Assembly.ReflectionOnlyLoadFrom(dependentAssemblyFilename);
            }
            return Assembly.ReflectionOnlyLoad(args.Name);
        }
        internal void LoadAssemblies(IEnumerable<string> assemblies)
        {
            foreach (string assemblyPath in assemblies)
            {
                try
                {
                    Assembly.ReflectionOnlyLoadFrom(assemblyPath);
                }
                catch (FileNotFoundException)
                {
                }
            }
        }
        private static ModuleInfo CreateModuleInfo(Type type)
        {
            string moduleName = type.Name;
            List<string> dependsOn = new List<string>();
            bool onDemand = false;
            CustomAttributeData moduleAttribute = CustomAttributeData.GetCustomAttributes(type).FirstOrDefault((CustomAttributeData cad) => cad.Constructor.DeclaringType!.FullName == typeof(ModuleAttribute).FullName);
            if (moduleAttribute != null)
            {
                foreach (CustomAttributeNamedArgument argument in moduleAttribute.NamedArguments)
                {
                    switch (argument.MemberInfo.Name)
                    {
                        case "ModuleName":
                            moduleName = (string)argument.TypedValue.Value;
                            break;
                        case "OnDemand":
                            onDemand = (bool)argument.TypedValue.Value;
                            break;
                        case "StartupLoaded":
                            onDemand = !(bool)argument.TypedValue.Value;
                            break;
                    }
                }
            }
            foreach (CustomAttributeData cad2 in from cad in CustomAttributeData.GetCustomAttributes(type)
                                                 where cad.Constructor.DeclaringType!.FullName == typeof(ModuleDependencyAttribute).FullName
                                                 select cad)
            {
                dependsOn.Add((string)cad2.ConstructorArguments[0].Value);
            }
            ModuleInfo obj = new ModuleInfo(moduleName, type.AssemblyQualifiedName)
            {
                InitializationMode = (InitializationMode)(onDemand ? 1 : 0),
                Ref = type.Assembly.EscapedCodeBase
            };
            obj.DependsOn.AddRange(dependsOn);
            return obj;
        }
    }
    /// <summary>
    /// Directory containing modules to search for.
    /// </summary>
    public string ModulePath { get; set; }
    /// <summary>
    /// Drives the main logic of building the child domain and searching for the assemblies.
    /// </summary>
    protected override void InnerLoad()
    {
        if (string.IsNullOrEmpty(ModulePath))
        {
            throw new InvalidOperationException("ModulePathCannotBeNullOrEmpty");
        }
        if (!Directory.Exists(ModulePath))
        {
            throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, ModulePath));
        }
        AppDomain childDomain = AppDomain.CurrentDomain;
        try
        {
            List<string> list = new List<string>();
            IEnumerable<string> assemblies = from Assembly assembly in AppDomain.CurrentDomain.GetAssemblies()
                                             where !(assembly is AssemblyBuilder) && assembly.GetType().FullName != "System.Reflection.Emit.InternalAssemblyBuilder" && !string.IsNullOrEmpty(assembly.Location)
                                             select assembly.Location;
            list.AddRange(assemblies);
         
            ///调整
                InnerModuleInfoLoader loader = new InnerModuleInfoLoader();
            this.Items.AddRange((IEnumerable<IModuleCatalogItem>)(object)loader.GetModuleInfos(ModulePath));
           
        }
        catch (Exception ex)
        {
            throw new Exception("There was an error loading assemblies.", ex);
        }
    }
}